Skip to content

服务端架构

基于 rts-server-golang cmd/server/ 解析 关键词: 单体架构, Room注册表, 连接管理, 可观测性, Prometheus

概述

服务端采用单体架构(Monolithic):所有组件(网络、房间管理、录制)在一个进程内,通过函数调用通信。

这对于 RTS 游戏是合理的——对战房间数量少(不像 MMORPG 万人在线),瓶颈在 CPU/内存而不是分布式协调。

进程结构

main()
├─ 初始化 logger (slog)
├─ 初始化 metrics registry (obs)
├─ 初始化 room registry
├─ 启动 UDP listener
├─ 启动 retransmission ticker (每 20ms)
├─ 启动 HTTP server (/metrics, /trace)
└─ 启动 signal handler (SIGINT/SIGTERM)

核心组件

1. Listener — UDP 网络层

go
listener, err := transport.Listen(transport.ListenerConfig{
    Addr:   ":9000",
    Logger: log,
})
listener.OnNewConn = func(conn *transport.Conn) {
    go handleConnection(conn, registry, defaults, log)
}
go listener.Serve()

每个新连接启动一个 goroutine 处理。

2. Room Registry — 房间管理

go
registry := room.NewRegistry(defaults, log)

// GetOrCreate: 根据 roomID 获取或创建房间
rm := registry.GetOrCreate(roomID)
playerID, ok := rm.AddPlayer(conn)

房间按需创建,人走房不自动销毁(由配置决定)。

3. Connection Handler — 连接生命周期

handleConnection(conn):
  1. read Hello (超时 5s) → wire.Decode → MsgHello
  2. send HelloAck
  3. read JoinRoom (超时 5s) → wire.Decode → MsgJoinRoom
  4. registry.GetOrCreate(roomID)
  5. rm.AddPlayer(conn) → 分配 playerID
  6. send JoinAck (含 Seed/MapW/MapH)
  7. 循环: conn.Inbox → rm.Inbox (消息转发)

4. Room — 游戏逻辑单元

每个房间独立运行一个 goroutine:

go
func (r *Room) Run(ctx context.Context) {
    for {
        // 定时 tick 循环(见 02_帧同步房间)
        select {
        case <-ctx.Done(): return
        case msg := <-r.Inbox: r.handleMsg(msg)
        }
    }
}

消息流向

客户端 UDP

transport.Conn (可靠UDP处理)
    ↓ conn.Inbox
handleConnection goroutine
    ↓ rm.Inbox <- RoomMsg
Room.Run (帧同步逻辑)
    ├─ sim.Step(world, cmds)
    ├─ broadcast FrameBundle
    ├─ hashAgg.Report
    └─ replayWriter.WriteTick

transport.Conn.Send (广播给所有玩家)

客户端

可观测性

Metrics — Prometheus 风格

go
metrics.Counter("rts_udp_packets_in_total")
metrics.Counter("rts_udp_retransmit_total")
metrics.Histogram("rts_udp_rtt_ms")
metrics.Histogram("rts_tick_duration_ms")
metrics.Gauge("rts_rooms_active")
metrics.Gauge("rts_input_delay_n")
metrics.Counter("rts_desync_total")

暴露在 GET /metrics

Trace — 最近帧的 trace

go
trace := obs.NewTraceRing(obs.DefaultTraceSize)

固定大小的环形 buffer,记录最近 N 帧的关键事件(tick 时间、seal 延迟、hash 状态)。暴露在 GET /trace

日志 — slog

go
log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
log.Info("player joined", "player_id", playerID, "room", roomID)
log.Warn("DESYNC DETECTED", "tick", tick)

启动参数

bash
./server \
  -addr :9000          \  # UDP 监听地址
  -http :9001          \  # HTTP 监控端口
  -tick-rate 20        \  # 每秒帧数
  -initial-n 3         \  # 初始输入延迟
  -players 2           \  # 每房间人数
  -seed 42             \  # 世界 seed
  -map-w 100 -map-h 100 \  # 地图尺寸
  -log-level info        # 日志级别

可靠性设计

设计作用
单体架构减少网络跳数,降低延迟
单 actor Room无锁并发,简化逻辑
录制写文件崩溃后可恢复
独立 goroutine per 连接连接隔离,一个崩了不影响其他

局限性

局限说明
单机瓶颈单机约 1000 并发房间(每房间 2 人)
无法水平扩展房间不能跨服务器
无分服合服房间固定在创建服务器

对于 RTS 1v1/2v2 对战(最多 4 人),单机足够。大型 MMO RTS 需要分布式改造(房间调度、服务发现、跨服通信)。

相关

撰写